//////////////////////////////////////////////
// BufferCastWrapper.h
//
//////////////////////////////////////////////

/// Defines / Macros -------------------------

#pragma once

/// Forward decl -----------------------------

namespace nkScripts
{
	class Environment ;
}

/// Includes ---------------------------------

// nkAstraeus
#include "../../../Dll/DllDefines.h"

// nkMemory
#include <NilkinsMemory/Containers/BufferCast.h>
#include <NilkinsMemory/Containers/StringView.h>

// nkScripts
#include <NilkinsScripts/Environments/Functions/Function.h>

#include <NilkinsScripts/Environments/UserTypes/ArrayAccessorDescriptor.h>
#include <NilkinsScripts/Environments/UserTypes/UserType.h>
#include <NilkinsScripts/Environments/UserTypes/UserTypeFieldDescriptor.h>

#include <NilkinsScripts/Environments/Environment.h>

// Standards
#include <string>
#include <type_traits>

/// Class ------------------------------------

namespace nkAstraeus::nkMemoryWrap
{
	template <typename T>
	class BufferCastWrapper final
	{
		public :

			// Environment update
			static void updateEnvironment (nkScripts::Environment* env, nkMemory::StringView typeName, nkMemory::StringView typeNameUserObject = "")
			{
				// Prepare the typename
				_typeName = typeName ;
				_typeNameUserObject = typeNameUserObject ;

				// Get the type
				nkScripts::UserType* arrayType = env->getUserType(typeName) ;

				// Callbacks setting
				// Constructor, destructor
				nkScripts::Function* func = arrayType->addStaticMethod("new") ;
				func->addParameter(nkScripts::FUNCTION_PARAMETER_TYPE::INT) ;
				func->setFunction(&constructor) ;

				arrayType->setDestructor(&destructor) ;

				// Array indexing enabling
				nkScripts::ArrayAccessorDescriptor arrayDesc ;
				arrayDesc._fieldType = _getArrayFieldType() ;
				arrayDesc._userTypeName = typeNameUserObject ;
				arrayDesc._readFunc = _arrayReadIndex<> ;
				arrayDesc._writeFunc = _arrayWriteIndex<> ;
				arrayType->enableArrayIndexing(arrayDesc) ;

				// Getters
				func = arrayType->addMethod("getData") ;
				func->setFunction(&getData) ;

				func = arrayType->addMethod("getSize") ;
				func->setFunction(&getSize) ;
			}

			// Callbacks
			// Constructor, destructor
			static nkScripts::OutputValue constructor (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* result = new nkMemory::BufferCast<T> (stack[0]._valInt) ;

				return nkScripts::OutputValue(result, _typeName, true) ;
			}

			static void destructor (void* toDestroy)
			{
				delete (nkMemory::BufferCast<T>*)toDestroy ;
			}

			// Getters
			static nkScripts::OutputValue getData (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;

				return nkScripts::OutputValue(nkMemory::StringView((char*)valUserCast->getData(), valUserCast->getSize())) ;
			}

			static nkScripts::OutputValue getSize (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;

				return nkScripts::OutputValue((int)valUserCast->getSize()) ;
			}

		private :

			// Functions
			// Constructor, destructor
			BufferCastWrapper () = delete ;
			~BufferCastWrapper () = delete ;

		private :

			// Specializations for integer types
			template <typename U = T>
			static typename std::enable_if_t<std::is_integral_v<U>, nkScripts::OutputValue>
			_arrayReadIndex (const nkScripts::DataStack& stack)
			{
				// Get the right item
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue((int)(*valUserCast)[index]) ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_integral_v<U>>
			_arrayWriteIndex (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;
				T newVal = (T)stack[2]._valInt ;

				(*valUserCast)[index] = newVal ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_integral_v<U>, nkScripts::FUNCTION_PARAMETER_TYPE>
			_getArrayFieldType ()
			{
				return nkScripts::FUNCTION_PARAMETER_TYPE::INT ;
			}

		private :

			// Specializations for floating types over 4 bytes
			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 4, nkScripts::OutputValue>
			_arrayReadIndex (const nkScripts::DataStack& stack)
			{
				// Get the right item
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue((float)(*valUserCast)[index]) ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 4>
			_arrayWriteIndex (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;
				T newVal = (T)stack[2]._valFloat ;

				(*valUserCast)[index] = newVal ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 4, nkScripts::FUNCTION_PARAMETER_TYPE>
			_getArrayFieldType ()
			{
				return nkScripts::FUNCTION_PARAMETER_TYPE::FLOAT ;
			}

			// Specializations for floating types over 8 bytes
			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 8, nkScripts::OutputValue>
			_arrayReadIndex (const nkScripts::DataStack& stack)
			{
				// Get the right item
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue((double)(*valUserCast)[index]) ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 8>
			_arrayWriteIndex (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;
				T newVal = (T)stack[2]._valDouble ;

				(*valUserCast)[index] = newVal ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_floating_point_v<U> && sizeof(U) == 8, nkScripts::FUNCTION_PARAMETER_TYPE>
			_getArrayFieldType ()
			{
				return nkScripts::FUNCTION_PARAMETER_TYPE::DOUBLE ;
			}

		private :

			// Specializations for structure types
			template <typename U = T>
			static typename std::enable_if_t<std::is_class_v<U>, nkScripts::OutputValue>
			_arrayReadIndex (const nkScripts::DataStack& stack)
			{
				// Get the right item
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue(&valUserCast[index], _typeName, false) ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_class_v<U>>
			_arrayWriteIndex (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;
				T* newVal = (T*)stack[2]._valUser._userData ;

				(*valUserCast)[index] = *newVal ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_class_v<U>, nkScripts::FUNCTION_PARAMETER_TYPE>
			_getArrayFieldType ()
			{
				return nkScripts::FUNCTION_PARAMETER_TYPE::USER_DATA_PTR ;
			}

		private :

			// Specializations for pointer over structure types
			template <typename U = T>
			static typename std::enable_if_t<std::is_pointer_v<U>, nkScripts::OutputValue>
			_arrayReadIndex (const nkScripts::DataStack& stack)
			{
				// Get the right item
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;

				return nkScripts::OutputValue(valUserCast[index], _typeName, false) ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_pointer_v<U>>
			_arrayWriteIndex (const nkScripts::DataStack& stack)
			{
				nkMemory::BufferCast<T>* valUserCast = (nkMemory::BufferCast<T>*)stack[0]._valUser._userData ;
				int index = stack[1]._valInt ;
				T* newVal = (T*)stack[2]._valUser._userData ;

				(*valUserCast)[index] = newVal ;
			}

			template <typename U = T>
			static typename std::enable_if_t<std::is_pointer_v<U>, nkScripts::FUNCTION_PARAMETER_TYPE>
			_getArrayFieldType ()
			{
				return nkScripts::FUNCTION_PARAMETER_TYPE::USER_DATA_PTR ;
			}

		private :

			inline static nkMemory::String _typeName ;
			inline static nkMemory::String _typeNameUserObject ;
	} ;
}

/// Exports ----------------------------------

namespace nkAstraeus::nkMemoryWrap
{
	template class DLL_ASTRAEUS_EXPORT BufferCastWrapper<int> ;
	template class DLL_ASTRAEUS_EXPORT BufferCastWrapper<float> ;
	template class DLL_ASTRAEUS_EXPORT BufferCastWrapper<double> ;
}